#pragma once
#include <climits>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
#include <iomanip>
#include "cmath"
#include "include/flow.h"

class PriorityAssignAlgorithm {
   public:
    PriorityAssignAlgorithm(flow* originFlows) {
        for (int i = 1; i <= flow_num; i++) {
            flows[i] = originFlows[i];
        }
    }
    virtual ~PriorityAssignAlgorithm() {}
    virtual void run()         = 0;
    virtual void debug_flows() = 0;
	void copy_flows(flow *flows) {
		for (int i = 1; i <= flow_num; i++) {
			flows[i] = this->flows[i];
		}
	}
   protected:
    flow     flows[FLOW_MAX_NUM];
    set<int> assign_flow_set[VM_NUM][2];
    set<int> ep_flow_set[VM_NUM][2];
    set<int> unassign_flow_set[VM_NUM][2];
    set<int> all_unassign_flow_set;
    set<int> all_ep_flow_set;
    set<int> temp_assign_set[VM_NUM][2];
    set<int> temp_unassign_set[VM_NUM][2];

    void cp_set(const set<int> (&src)[VM_NUM][2], set<int> (&dst)[VM_NUM][2]) {
        for (int i = 0; i < VM_NUM; i++) {
            dst[i][0] = src[i][0];
            dst[i][1] = src[i][1];
        }
    }

    void clear_assign() {
        for (int i = 0; i < VM_NUM; i++) {
            assign_flow_set[i][0].clear();
            assign_flow_set[i][1].clear();
        }
    }

    void clear_ep() {
        for (int i = 0; i < VM_NUM; i++) {
            ep_flow_set[i][0].clear();
            ep_flow_set[i][1].clear();
        }
        all_ep_flow_set.clear();
    }

    void clear_unassign() {
        for (int i = 0; i < VM_NUM; i++) {
            unassign_flow_set[i][0].clear();
            unassign_flow_set[i][1].clear();
        }
        all_unassign_flow_set.clear();
    }

    bool assign_all() {
        return all_unassign_flow_set.empty();
    }

    void erase_unassign(int id) {
        all_unassign_flow_set.erase(id);
        unassign_flow_set[flows[id].src][0].erase(id);
        unassign_flow_set[flows[id].dst][1].erase(id);
    }

    void add_unassign(int id) {
        unassign_flow_set[flows[id].src][0].insert(id);
        unassign_flow_set[flows[id].dst][1].insert(id);
        all_unassign_flow_set.insert(id);
    }

    void add_assign(int id) {
        assign_flow_set[flows[id].src][0].insert(id);
        assign_flow_set[flows[id].dst][1].insert(id);
    }

    void add_ep(int id) {
        ep_flow_set[flows[id].src][0].insert(id);
        ep_flow_set[flows[id].dst][1].insert(id);
        all_ep_flow_set.insert(id);
    }

    void erase_ep(int id) {
        ep_flow_set[flows[id].src][0].erase(id);
        ep_flow_set[flows[id].dst][1].erase(id);
        all_ep_flow_set.erase(id);
    }

    double get_max_lp_source(int vm_id) {
        double res = 0;
        for (const auto &f: assign_flow_set[vm_id][0]) {
            res = max(res, flows[f].cw);
        }
        return res;
    }

    double get_max_lp_dest(int vm_id) {
        double res = 0;
        for (const auto &f: assign_flow_set[vm_id][1]) {
            res = max(res, flows[f].cr);
        }
        return res;
    }
    double get_max_ep_source(int vm_id) {
        double res = 0;
        for (const auto &f: ep_flow_set[vm_id][0]) {
            res = max(res, flows[f].cw);
        }
        return res;
    }

    double get_max_ep_dest(int vm_id) {
        double res = 0;
        for (const auto &f: ep_flow_set[vm_id][1]) {
            res = max(res, flows[f].cr);
        }
        return res;
    }

    double R_SOURCE_ANALYZE(const flow& cur_flow) {
        const double block_time = get_max_lp_source(cur_flow.src);               // 低优先级阻塞时间
        const double queue_time = get_max_ep_source(cur_flow.src) * QUEUE_SIZE;  // 同优先级排队时间

        double cur = 0, prev = cur_flow.cw;
        while (true) {
            cur = block_time + queue_time;
            for (auto& h : unassign_flow_set[cur_flow.src][0]) {
                if (cur_flow.id == flows[h].id) continue;
                cur += ceil((prev + CBIT_WRITE) / flows[h].period) * flows[h].cw;
            }
            if (IsSame(cur, prev)) {
                return cur + cur_flow.cw;
            }
            if (cur + cur_flow.cw > cur_flow.relative_dl + DIFF) {
                return UNSCHEDULABLE;
            }
            prev = cur;
        }
    }

    double R_DEST_ANALYZE(const flow& cur_flow, double r_source) {
        const double block_time = get_max_lp_dest(cur_flow.dst);               // 低优先级阻塞时间
        const double queue_time = get_max_ep_dest(cur_flow.dst) * QUEUE_SIZE;  // 同优先级排队时间

        double cur = 0, prev = cur_flow.cr;
        while (true) {
            cur = block_time + queue_time;
            for (auto& h : unassign_flow_set[cur_flow.dst][1]) {
                if (cur_flow.id == flows[h].id) continue;
                cur += ceil((prev + CBIT_READ) / flows[h].period) * flows[h].cr;
            }
            if (IsSame(cur, prev)) {
                return cur + cur_flow.cr;
            }
            if (cur + cur_flow.cr + r_source > cur_flow.relative_dl + DIFF) {
                return UNSCHEDULABLE;
            }
            prev = cur;
        }
    }

    bool WCRT(flow& cur_flow) {
        double r_source = R_SOURCE_ANALYZE(cur_flow);
        double r_dest   = R_DEST_ANALYZE(cur_flow, r_source);
        cur_flow.wcrt = r_source + r_dest;
        if (IsSame(r_source, UNSCHEDULABLE) || IsSame(r_dest, UNSCHEDULABLE) ||
            r_source + r_dest > cur_flow.relative_dl + DIFF) {
            return false;
        }
        return true;
    }

    double print_wcrt(double wcrt){
        if (IsSame(wcrt, UNSCHEDULABLE) || wcrt >= UNSCHEDULABLE) {
            return -1;
        }
        return wcrt;
    }
};
